package edu.towson.cosc.cosc455.aloiac1.markdowncompiler;

import edu.towson.cosc.cosc455.interfaces.SyntaxAnalyzer;

public class MySyntaxAnalyzer implements SyntaxAnalyzer {
	
	private MyLexicalAnalyzer lexer = new MyLexicalAnalyzer();
	/** Define index to determine where in array to place new NAME / VALUE */
	private int defineIndex=0;
	/** Checks to see if DEFINE has been overwritten */
	private boolean overwrittenName=false;

	/**
	 * This method implements the BNF grammar rule for the document annotation.
	 * @throws CompilerException
	 */
	@Override
	public void document() throws CompilerException {
		/** Very first token */
		lexer.getNextToken();
		if(!Compiler.token.equalsIgnoreCase("#START DOCUMENT")) {
			String message = "A #START DOCUMENT annotation was expected when a " + 
					Compiler.token + " was found!";
			throw new CompilerException(message);
		}
		else {
			pushToken();
			lexer.getNextToken();
			define(0);
			head();
			body();
			
			if(!Compiler.token.equalsIgnoreCase("#END DOCUMENT")) {
				String message = "A #END DOCUMENT annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else
				pushToken();
		}
		/** Checks to see if there is any annotations after #END DOCUMENT */
		if(Compiler.textFromFile.length() != 0 && Compiler.checkForCharacters()) {
			String message = "Unexpected characters after #END DOCUMENT. Please delete the following: " + Compiler.textFromFile;
			throw new CompilerException(message);
        }
		Compiler.canWrite=true;
	}
	
	/**
	 * This method implements the BNF grammar rule for the body annotation.
	 * @throws CompilerException
	 */
	public void body() throws CompilerException {
			if(Compiler.token.equalsIgnoreCase("#START PARAGRAPH")) {
                paragraph();
                body();
			}
			else if(Compiler.token.equalsIgnoreCase("#START BOLD")) {
                bold();
                body();
			}
			else if(Compiler.token.equalsIgnoreCase("#START ITALICS")) {
                italics();
                body();
			}
			else if(Compiler.token.equalsIgnoreCase("#START LIST")) {
                list();
                body();
			}
			else if(Compiler.token.equalsIgnoreCase("#NEWLINE")) {
                newline();
                body();
			}
			else if(!(Compiler.token.charAt(0)=='#')) { 
				innerText();
				body();
			}
			else
				return;
	}

	/**
	 * This method implements the BNF grammar rule for the head annotation.
	 * @throws CompilerException
	 */
	@Override
	public void head() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START HEAD")) {
			pushToken();
			lexer.getNextToken();
			title();
			
			if(!Compiler.token.equalsIgnoreCase("#END HEAD")) {
				String message = "A #END HEAD annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the title annotation.
	 * @throws CompilerException
	 */
	@Override
	public void title() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START TITLE")) {
			pushToken();
			lexer.getNextToken();
			text();
			
			if(!Compiler.token.equalsIgnoreCase("#END TITLE")) {
				String message = "A #END TITLE annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the paragraph annotation.
	 * @throws CompilerException
	 */
	@Override
	public void paragraph() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START PARAGRAPH")) {
			pushToken();
			lexer.getNextToken();
			define(1);
			innerParagraph();
			
			if(!Compiler.token.equalsIgnoreCase("#END PARAGRAPH")) {
				String message = "A #END PARAGRAPH annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}
	
	/**
	 * This method implements the BNF grammar rule for the inner-paragraph annotation.
	 * @throws CompilerException
	 */
	public void innerParagraph() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START BOLD")) {
            bold();
            innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#START ITALICS")) {
            italics();
            innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#START LIST")) {
            list();
            innerParagraph();
		}
		else if(Compiler.token.equalsIgnoreCase("#NEWLINE")) {
            newline();
            innerParagraph();
		}
		else if(!(Compiler.token.charAt(0)=='#')) { 
			innerText();
			innerParagraph();
		}
		else
			return;
	}

	/**
	 * This method implements the BNF grammar rule for the bold annotation.
	 * @throws CompilerException
	 */
	@Override
	public void bold() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START BOLD")) {
			pushToken();
			lexer.getNextToken();
			define(4);
			innerText();
			
			if(!Compiler.token.equalsIgnoreCase("#END BOLD")) {
				String message = "A #END BOLD annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the italics annotation.
	 * @throws CompilerException
	 */
	@Override
	public void italics() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START ITALICS")) {
			pushToken();
			lexer.getNextToken();
			define(4);
			innerText();
			
			if(!Compiler.token.equalsIgnoreCase("#END ITALICS")) {
				String message = "A #END ITALICS annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the list annotation.
	 * @throws CompilerException
	 */
	@Override
	public void list() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START LIST")) {
			pushToken();
			lexer.getNextToken();
			
			if(Compiler.token.equalsIgnoreCase("#START ITEM")) {
				pushToken();
				lexer.getNextToken();
				define(2);
				innerList();
				
				if(!Compiler.token.equalsIgnoreCase("#END ITEM")) {
					String message = "A #END ITEM annotation was expected when a " + 
							Compiler.token + " was found!";
					throw new CompilerException(message);
				}
				else {
					pushToken();
					lexer.getNextToken();
				}
			}
			item();
			if(!Compiler.token.equalsIgnoreCase("#END LIST")) {
				String message = "A #END LIST annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}
	
	/**
	 * This method implements the BNF grammar rule for the inner-list annotation.
	 * @throws CompilerException
	 */
	public void innerList() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START BOLD")) {
			bold();
            innerList();
		}
	    else if(Compiler.token.equalsIgnoreCase("#START ITALICS")) { 
	    	italics();
	    	innerList();
	    }
	    else if(Compiler.token.equalsIgnoreCase("#START LIST")) { 
	    	list();
	    	innerList();
	    }
	    else if(Compiler.token.equalsIgnoreCase("#START USE")) { 
	    	use();
	    	innerList();
	    }
	    else if(!(Compiler.token.charAt(0)=='#')) {
	    	innerText();
	    	innerList();
	    }
	    else
	    	return;
	}

	/**
	 * This method implements the BNF grammar rule for the item annotation.
	 * @throws CompilerException
	 */
	@Override
	public void item() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START ITEM")) {
			pushToken();
			lexer.getNextToken();
			define(3);
			innerList();
			
			if(!Compiler.token.equalsIgnoreCase("#END ITEM")) {
				String message = "A #END ITEM annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
		if(Compiler.token.equalsIgnoreCase("#START ITEM"))
			item();
	}

	/**
	 * This method implements the BNF grammar rule for the audio annotation.
	 * @throws CompilerException
	 */
	@Override
	public void audio() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START AUDIO")) {
			pushToken();
			lexer.getNextToken();
			text();
			
			if(!Compiler.token.equalsIgnoreCase("#END AUDIO")) {
				String message = "A #END AUDIO annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the video annotation.
	 * @throws CompilerException
	 */
	@Override
	public void video() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START VIDEO")) {
			pushToken();
			lexer.getNextToken();
			text();
			
			if(!Compiler.token.equalsIgnoreCase("#END VIDEO")) {
				String message = "A #END VIDEO annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the define annotation.
	 * @throws CompilerException
	 */
	@Override
	public void define(int precedence) throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START DEFINE")) {
			Compiler.token="";
			lexer.getNextToken();
			
			if(Compiler.token.equalsIgnoreCase("#NAME ")) {
				String message = "A #NAME annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				/**
				 * Checks NAME against DEFINE
				 * if NAME exists change boolean variable
				 * if not input regularly
				 */
				lexer.getNextToken();
				if (Compiler.inDefine())
					overwrittenName=true;
				else {
					Compiler.define[defineIndex] = Compiler.token;
					defineIndex++;
				}
				Compiler.token="";
			}
			
			lexer.getNextToken();
			
			if(Compiler.token.equalsIgnoreCase("#VALUE ")) {
				String message = "A #VALUE annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				/**
				 * Recieves boolean variable
				 * if NAME exists overwrite VALUE
				 * if not input regularly
				 */
				lexer.getNextToken();
				if (overwrittenName) {
					Compiler.define[Compiler.defineNewIndex+1] = Compiler.token;
					overwrittenName=false;
				}
				else {
					Compiler.define[defineIndex] = Compiler.token;
					defineIndex++;
				}
				Compiler.token="";
			}
			
			lexer.getNextToken();
			
			if(Compiler.token.equalsIgnoreCase("#END DEFINE ")) {
				String message = "A #END DEFINE annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				Compiler.token="";
				lexer.getNextToken();
				}
			
			if(Compiler.token.equalsIgnoreCase("#START DEFINE")) 
				define(precedence+1);
			else
				return;
		}
	}

	/**
	 * This method implements the BNF grammar rule for the use annotation.
	 * @throws CompilerException
	 */
	@Override
	public void use() throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START USE")) {
			pushToken();
			lexer.getNextToken();
			text();
			
			if(!Compiler.token.equalsIgnoreCase("#END USE")) {
				String message = "A #END USE annotation was expected when a " + 
						Compiler.token + " was found!";
				throw new CompilerException(message);
			}
			else {
				pushToken();
				lexer.getNextToken();
			}
		}
	}

	/**
	 * This method implements the BNF grammar rule for the newline annotation.
	 * @throws CompilerException
	 */
	public void newline() {
		if(Compiler.token.equalsIgnoreCase("#NEWLINE")) {
			pushToken(); 
			lexer.getNextToken();
		}
	}
	
	/**
	 * This method implements the BNF grammar rule for the text annotation.
	 * @throws CompilerException
	 */
	public void text() {
		if(!(Compiler.token.charAt(0)=='#')) {
			pushToken();
			lexer.getNextToken();
		}
	}
	
	/**
	 * This method implements the BNF grammar rule for the inner-text annotation.
	 * @throws CompilerException
	 */
	public void innerText () throws CompilerException {
		if(Compiler.token.equalsIgnoreCase("#START USE")) {
			use();
            innerText();
		}
	    else if(Compiler.token.equalsIgnoreCase("#NEWLINE")) { 
	    	newline();
	    	innerText();
	    }
	    else if(!(Compiler.token.charAt(0)=='#')) {
	    	text();
	    	innerText();
	    }
	    else
	    	return;
	}
	
	/** Method to add the token to a stack and then resets the token */
	public void pushToken() {
		Compiler.completeTokens.push(Compiler.token);
		Compiler.token="";
	}
}
